use std::path::Path;

use common_base::{
    dashmap::{mapref::one::Ref, DashMap},
    once_cell::sync::{Lazy, OnceCell},
    prelude::*,
    serde_json,
    tokio::{fs::File, io::AsyncReadExt},
};

use crate::{data::ParamObject, support::DylibRepository};

use super::{
    service_def::ServiceContainerStub,
    service_model::{EventContainer, ServiceAppDesktop},
};

static SERVICE_CONTAINER_DIR_ROOT: OnceCell<String> = OnceCell::new();

static DYLIB_REPO: OnceCell<DylibRepository> = OnceCell::new();
static SERVICE_APP_DESKTOP_REPO: Lazy<DashMap<String, ServiceAppDesktop>> =
    Lazy::new(|| DashMap::default());

pub struct ServiceBuilder;

impl ServiceBuilder {
    pub fn initialize(service_container_dir: &str, service_dylib_dir: &str) {
        SERVICE_CONTAINER_DIR_ROOT
            .set(service_container_dir.to_owned())
            .expect("ServiceBuilder:service_container_dir should not initialize twice");

        DYLIB_REPO
            .set(DylibRepository::new(service_dylib_dir))
            .expect("ServiceBuilder:service_dylib_dir should not initialize twice");
    }

    pub async fn get_service_app_desktop(
        param_object: &mut ParamObject,
    ) -> Result<Ref<'static, String, ServiceAppDesktop>> {
        let service_key = param_object.get_value_by_env_name("ServiceKey").to_owned();
        if service_key.is_empty() {
            return Err(ErrorCode::ServiceNotFound("service_key is empty."));
        }

        let service_app_container =
            ServiceBuilder::get_service_app_desktop_(param_object, service_key.as_str()).await?;

        // TODO: why should we do this?
        // {
        //     let _guard = service_app_container.lock();
        //     let sid_empty = _guard.borrow().service_container.get_id().is_empty();
        //     if sid_empty {
        //         _guard
        //             .borrow_mut()
        //             .service_container
        //             .set_id(service_key);
        //     }
        // }

        Ok(service_app_container)
    }

    async fn get_service_app_desktop_(
        param_object: &mut ParamObject,
        service_key: &str,
    ) -> Result<Ref<'static, String, ServiceAppDesktop>> {
        if let Some(sadp) = SERVICE_APP_DESKTOP_REPO.get(service_key) {
            Ok(sadp)
        } else {
            // TODO: BBZD_SBLX: what's mean?
            param_object.set_value_by_param_name("BBZD_SBLX", "d".to_owned());
            let srv_def_data = Self::load_service_definition(param_object, service_key).await?;

            SERVICE_APP_DESKTOP_REPO.insert(
                service_key.to_owned(),
                Self::generate_service_app_desktop(srv_def_data.as_slice())?,
            );
            Ok(SERVICE_APP_DESKTOP_REPO.get(service_key).unwrap())
        }
    }

    async fn load_service_definition(
        param_object: &ParamObject,
        service_key: &str,
    ) -> Result<Vec<u8>> {
        let dev_srv_container_fp = format!(
            "{}/ServiceDevSpace/{}.json",
            SERVICE_CONTAINER_DIR_ROOT
                .get()
                .expect("ServiceBuilder should be initialize but not"),
            service_key
        );
        debug!("dev_srv_container file path: {}", dev_srv_container_fp);

        let srv_container_fp = if Path::new(dev_srv_container_fp.as_str()).exists() {
            dev_srv_container_fp
        } else {
            let typ = if param_object.get_value_by_param_name("BBZD_SBLX").eq("d") {
                "ServiceSpace"
            } else {
                "FormSpace"
            };
            let srv_container_fp = format!(
                "{}/{}/{}.json",
                SERVICE_CONTAINER_DIR_ROOT
                    .get()
                    .expect("ServiceBuilder should be initialize but not"),
                typ,
                service_key
            );

            debug!("srv_container file path: {}", srv_container_fp);
            srv_container_fp
        };

        match File::open(srv_container_fp.as_str()).await {
            Ok(mut file) => {
                let mut bytes = Vec::with_capacity(4096);
                let file_size = file.read_to_end(&mut bytes).await?;
                debug!(
                    "read {} bytes from {} for service {}.",
                    file_size, srv_container_fp, service_key
                );
                Ok(bytes)
            }
            Err(e) => {
                info!(
                    "can not read file from {} for service {}, casuse {}.",
                    srv_container_fp, service_key, e
                );

                // TODO: select BBZD_GS from SYS_DBFORMGS where BBZD_BH={service_key}

                todo!()
            }
        }
    }

    fn generate_service_app_desktop(srv_def: &[u8]) -> Result<ServiceAppDesktop> {
        let service_container_stub = serde_json::from_slice::<ServiceContainerStub>(srv_def)?;

        let event_container = EventContainer::get_instance("eventContainer", "eventContainer");

        // TODO: why should do this?
        // event_container.set_data_container(&service_container);

        let srv_cntr = service_container_stub.generate_service_container(
            DYLIB_REPO
                .get()
                .expect("DYLIB_REPO should be initialize but not."),
        )?;

        Ok(ServiceAppDesktop::new(srv_cntr, event_container))
    }
}
